package api

import (
	"encoding/json"
	"pan/common/conf"
	"pan/common/exception"
	"pan/common/log"
	"pan/common/net"
	"pan/common/util"
)

const (
	DOWNLOAD_AUTHTYPE = "QUERY_STRING"
)

type DownloadRequest struct {
	Docid    string `json:"docid"`
	Rev      string `json:"rev"`
	AuthType string `json:"authtype"`
	ReqHost  string `json:"reqhost"`
	UseHttps bool   `json:"usehttps"`
	SaveName string `json:"savename"`
}

type DownloadResponse struct {
	FailRsponse

	NeedWatermark bool     `json:"need_watermark"`
	Rev           string   `json:"rev"`
	Name          string   `json:"name"`
	Editor        string   `json:"editor"`
	Modified      int64    `json:"modified"`
	Size          int64    `json:"size"`
	ClientMTime   int64    `json:"client_mtime"`
	AuthRequest   []string `json:"authrequest"`
}

type DownloadCallback struct {
	Start    net.DownloadStart
	Progress net.DownloadProgress
	Finish   net.DownloadFinish
}

func DownloadApi(savedDir, tokenid string, req *DownloadRequest, callback *DownloadCallback) error {
	resp, err := downloadSetup1(tokenid, req)
	if err != nil {
		return err
	}
	return downloadSetup2(savedDir, req, resp, callback)
}

func downloadSetup1(tokenid string, req *DownloadRequest) (*DownloadResponse, error) {
	url := util.PathJoin(conf.GlobalConfig.Url, conf.GlobalConfig.Paths.Download)

	if log.IsDebugEnable() {
		bytes, _ := json.Marshal(req)
		log.Debug("download setup1 post: ", url, ", token:", tokenid, ", body:", string(bytes))
	}

	resp := &DownloadResponse{}
	if err := net.PostWithToken(url, tokenid, req, resp); err != nil {
		return nil, err
	}

	if log.IsDebugEnable() {
		result, _ := json.Marshal(resp)
		log.Debug("download setup1 response: ", string(result))
	}

	if resp.Errcode != 0 {
		return nil, exception.NewExpectError("download failed, %s", resp.FailString())
	}
	return resp, nil
}

func downloadSetup2(savedDir string, req *DownloadRequest, setup1Resp *DownloadResponse, callback *DownloadCallback) error {
	if setup1Resp.AuthRequest == nil || len(setup1Resp.AuthRequest) < 2 {
		return exception.NewExpectError("download response authrequest invalid")
	}
	url := setup1Resp.AuthRequest[1]
	if req.AuthType == DOWNLOAD_AUTHTYPE { // 直接发起Get请求
		return redirectOrDownload(url, make(map[string]string), savedDir, setup1Resp.Name, callback)
	} else { // 需要在header中添加参数
		header := make(map[string]string)
		// 提取header参数, 需要跳过数组的前2个下标
		for i := len(setup1Resp.AuthRequest) - 1; i > 1; i-- {
			k, v := util.SplitKeyValue(setup1Resp.AuthRequest[i], ":")
			if k != "" {
				header[k] = v
			}
		}
		return redirectOrDownload(url, header, savedDir, setup1Resp.Name, callback)
	}
}

func redirectOrDownload(url string, header map[string]string, savedDir, defaultFilename string, callback *DownloadCallback) error {
	client := net.NewHttpClient().SetUrl(url).SetMethod(net.HTTP_GET).SetHeader(header)
	client.SetDownloadStart(callback.Start).SetDownloadProgress(callback.Progress).SetDownloadFinish(callback.Finish)
	client.SetStatusCodeHandle(func(statusCode int) error {
		if statusCode == 307 {
			return &NeedRedirectError{}
		}
		return nil
	})

	if log.IsDebugEnable() {
		headerStr, _ := json.Marshal(header)
		log.Debug("download setup2 get: ", url, ", header:", string(headerStr), ", savedDir:", savedDir, ", defaultFilename:", defaultFilename)
	}

	rsp, err := client.Download(savedDir, defaultFilename)
	if err == nil {
		return nil
	}
	if _, ok := err.(*NeedRedirectError); ok {
		// redirect download
		url = rsp.GetHeader("location")
		if url == "" {
			return exception.NewExpectError("cannot redirect empty url")
		}

		if log.IsDebugEnable() {
			headerStr, _ := json.Marshal(header)
			log.Debug("download setup3 get: ", url, ", header:", string(headerStr), ", savedDir:", savedDir, ", defaultFilename:", defaultFilename)
		}

		cli := net.NewHttpClient().SetUrl(url).SetMethod(net.HTTP_GET).SetHeader(header)
		cli.SetDownloadStart(callback.Start).SetDownloadProgress(callback.Progress).SetDownloadFinish(callback.Finish)
		_, err = cli.Download(savedDir, defaultFilename)
		if err == nil {
			return nil
		}
		return err
	} else {
		return err
	}
}

type NeedRedirectError struct {
}

func (r *NeedRedirectError) Error() string {
	return "need redirect"
}
